# Copyright (C) 2007, The Perl Foundation.
# $Id: grammar-actions.pl 23132 2007-11-27 16:49:58Z pmichaud $

=head1 NAME

FUN::Grammar::Actions - ast transformations for FUN

=head1 DESCRIPTION

This file contains the methods that are used by the parse
grammar to build the PAST representation of an abc program.
Each method below corresponds to a rule in F<grammar.pg>,
and is invoked at the point where C<{*}> appears in the rule,
with the current match object as the first argument.  If the
line containing C<{*}> also has a C<#= key> comment, then the
value of the comment is passed as the second argument to the
method.

Some hopefully helpful hints for reading this file:

=over 4

=item

It often helps to refer to the rules in F<grammar.pg> when 
looking at the corresponding methods here.

=item

Within a method, C<< $<foo> >> refers to the named capture C<foo>
within the C< $/ > match object.  Normally this is either another
match object or an array of match objects.

=item

The C<make> function and C< $(...) > are used to set and retrieve
the I<result object> for a match.  Here, we use the result object
to hold the ast representation of any given match.  So, in the
code below, whenever you see an expression like C<< $($<foo>) >>,
we're really saying "the ast of C<< <foo> >>".

=back

=cut

class FUN::Grammar::Actions;

method TOP($/) {
    my $program := PAST::Block.new( 
                        $( $<app_expr> ), 
                        :node($/), 
                        :blocktype('declaration') );

    make PAST::Op.new(  PAST::Op.new(
                            $program, :pasttype('call')),
                        :name('display'),
                        :pasttype('call') );
}

##  if_statement:
##    After parsing an if statement, the conditional
##    expression will be in $<test>, the "then"
##    statement will be in $<primary>, and the
##    "else" expression will be in $<alternate>.
##    So, we just obtain the asts of these subrule
##    matches and set them as the children of a
##    PAST::Op node with a pasttype of 'if'.
method if_expr($/) {
    make PAST::Op.new( $( $<test> ),
                       $( $<primary> ),
                       $( $<alternate> ),
                       :pasttype('if'),
                       :node( $/ ) );
}

## A "let x = e1 in e2" expression creates a new environment with a new variable (x)
## bound to the result of e1.  Once that environment is created e2 is evaluated inside it.
## PAST::Block with a :blocktype('immediate') provides these semantics.
method let_expr($/) {
    my $var := $( $<var> );
    my $val := $( $<val> );
    $var.isdecl(1);

    my $bind := PAST::Op.new( $var,
                              $val,
                              :pasttype('bind') );

    my $past := PAST::Block.new( $bind,
                                 $( $<body> ), 
                                 :node($/), 
                                 :blocktype('immediate') );
    $past.symbol( $var.name(), :scope('lexical') );

    make $past;
}

## Generates the Abstract syntax of "fn x => e" which creates a function closure of the environment
## in which it is created.
## PAST::Block with a :blocktype('declaration') will create a function, but not the closure.  To
## get the closure the block needs to be created into a closure with some custom PIR code.
## TODO: it seems like there should be a PAST node to create a closure
method fn_expr($/) {
    my $param := $( $<var> );
    $param.scope('parameter');

    my $block := PAST::Block.new( $param,
                                  $( $<body> ),
                                  :node($/), 
                                  :blocktype('declaration') );
    $block.symbol($param.name(), :scope('lexical'));

    ## The PAST::Block simply creates a subroutine that has 
    ## its lexical scope chained (for purposes of variable lookup) 
    ## to the parent block's (a PAST::Block higher up in the AST).  
    ## In order to create a closure (bind to an instance of bhe 
    ## lexical scope) we need to call newclosure on it as well.
    make PAST::Op.new( $block,
                       :pasttype('inline'),
                       :inline("newclosure %0, %0\n%r = %0"));
}

## "fun f x => e" also creates a function closure (like fn) of the environment
## in which it is created.  However the f variable in the declaration is bound to the
## function itself.  Creating the semantics for this is a little tricky, but ends up being 
## forward.  We rely on the fact that "fun f x => e" is the same as "let f = fn x => e in f"
## and create the structure accordingly
method fun_expr($/) {
    my $func := $( $<func> );
    $func.isdecl(1);
    $func.scope('lexical');

    my $param := $( $<var> );
    $param.scope('parameter');

    my $block := PAST::Block.new( $param, 
                                  $( $<body> ),
                                  :node($/), 
                                  :blocktype('declaration') );
    $block.symbol($param.name(), :scope('lexical'));

    my $bind := PAST::Op.new( $func,
                              PAST::Op.new( $block,
                                            :pasttype('inline'),
                                            :inline("newclosure %0, %0\n%r = %0")),
                              :pasttype('bind'));

    my $outer_scope := PAST::Block.new( $bind, :blocktype('immediate') );
    $outer_scope.symbol($func.name(), :scope('lexical'));

    make $outer_scope;
}

## Because of the way the grammar is constructed an app_expr is not always going to be
## a function application.  When it isn't there is nothing to do, when it is the 
## function application is simply a call.
method app_expr($/) {
    if($<operand>) {
        my $operator := $( $<operator> );
        my $operand := $( $<operand>[0] );

        make PAST::Op.new( $operator,
                           $operand,
                           :node($/), 
                           :pasttype('call') );
    }
    else {
        make $( $<operator> );
    }
}

method term($/, $key) {
    make $( $/{$key} );
}

method integer($/) {
    make PAST::Val.new( :value( ~$/ ), :returns('Integer'), :node( $/ ) );
}

method float($/) {
    make PAST::Val.new( :value( ~$/ ), :returns('Float'), :node( $/ ) );
}

##  expression:
##    This is one of the more complex transformations, because
##    our grammar is using the operator precedence parser here.
##    As each node in the expression tree is reduced by the
##    parser, it invokes this method with the operator node as
##    the match object and a $key of 'reduce'.  We then build
##    a PAST::Op node using the information provided by the
##    operator node.  (Any traits for the node are held in $<top>.)
##    Finally, when the entire expression is parsed, this method
##    is invoked with the expression in $<expr> and a $key of 'end'.
method expression($/, $key) {
    if ($key eq 'end') {
        make $($<expr>);
    }
    else {
        my $past := PAST::Op.new( :name($<type>),
                                  :pasttype($<top><pasttype>),
                                  :pirop($<top><pirop>),
                                  :lvalue($<top><lvalue>),
                                  :node($/) );
        for @($/) {
            $past.push( $($_) );
        }
        make $past;
    }
}

method var($/) {
    make PAST::Var.new( :name( ~$<name> ), :node( $/ ) );
}
